package com.symaster.backups.scheduling;

import cn.hutool.core.io.IoUtil;
import com.symaster.backups.enumerate.FileSystemStatus;
import com.symaster.backups.enumerate.PathType;
import com.symaster.backups.manager.EventManager;
import com.symaster.backups.manager.TaskManager;
import com.symaster.backups.model.FileSystemMonitorBackupItemModel;
import com.symaster.backups.model.FileSystemMonitorMessageModel;
import com.symaster.backups.model.FileSystemMonitorModel;
import com.symaster.backups.repository.FileSystemMonitorBackupItemRepository;
import com.symaster.backups.repository.FileSystemMonitorMessageRepository;
import com.symaster.backups.repository.FileSystemMonitorRepository;
import com.symaster.backups.service.SystemConfigService;
import com.symaster.common.util.IOUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author yinmiao
 * @version 2024/2/18
 */
@Component
public class FileMonitor implements Runnable {

    private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd");
    private final FileSystemMonitorRepository fileSystemMonitorRepository;
    private final FileSystemMonitorMessageRepository fileSystemMonitorMessageRepository;
    private final FileSystemMonitorBackupItemRepository fileSystemMonitorBackupItemRepository;
    private final SystemConfigService systemConfigService;
    private final EventManager eventManager;
    private final TaskManager taskManager;

    @Autowired
    public FileMonitor(FileSystemMonitorRepository fileSystemMonitorRepository,
                       FileSystemMonitorMessageRepository fileSystemMonitorMessageRepository,
                       FileSystemMonitorBackupItemRepository fileSystemMonitorBackupItemRepository,
                       SystemConfigService systemConfigService, EventManager eventManager, TaskManager taskManager) {
        this.fileSystemMonitorRepository = fileSystemMonitorRepository;
        this.fileSystemMonitorMessageRepository = fileSystemMonitorMessageRepository;
        this.fileSystemMonitorBackupItemRepository = fileSystemMonitorBackupItemRepository;
        this.systemConfigService = systemConfigService;
        this.eventManager = eventManager;
        this.taskManager = taskManager;
    }

    @Scheduled(fixedDelay = 5000)
    public void monitorAll() {
        taskManager.taskExec("FileMonitor", this);
    }

    public void run() {
        for (FileSystemMonitorModel fileSystemMonitorModel : fileSystemMonitorRepository.monitorAll()) {
            File file = new File(fileSystemMonitorModel.getPathName());

            if (!file.exists()) {
                pathNotExists(fileSystemMonitorModel);
            } else {

                if (file.isDirectory()) {
                    fileSystemMonitorModel.setFileCount(0);
                    fileSystemMonitorModel.setFolderCount(0);
                    pathIsDirectory(fileSystemMonitorModel);
                } else {
                    pathIsFile(fileSystemMonitorModel);
                    fileSystemMonitorModel.setFileCount(1);
                }

                fileSystemMonitorModel.setLastScan(new Date());
                fileSystemMonitorRepository.save(fileSystemMonitorModel);
                eventManager.trigger("MONITOR_UPDATE", fileSystemMonitorModel);

                if (FileSystemStatus.INIT == FileSystemStatus.ofCode(fileSystemMonitorModel.getStatus())) {
                    fileSystemMonitorModel.setStatus(FileSystemStatus.COMPLETE.getCode());
                    fileSystemMonitorModel.setUpdatedTime(new Date());
                    fileSystemMonitorRepository.save(fileSystemMonitorModel);
                }
            }
        }
    }

    private void pathIsFile(FileSystemMonitorModel fileSystemMonitorModel) {
        File file = new File(fileSystemMonitorModel.getPathName());

        List<FileSystemMonitorBackupItemModel> backupItems = fileSystemMonitorBackupItemRepository.find(fileSystemMonitorModel.getId());
        if (isUpdate(backupItems, file.lastModified())) {
            backup(file, fileSystemMonitorModel, null);
        }
    }

    private void pathIsDirectory(FileSystemMonitorModel fileSystemMonitorModel) {
        File file = new File(fileSystemMonitorModel.getPathName());
        File[] files = file.listFiles();
        if (files == null) {
            return;
        }

        for (File file1 : files) {
            if (file1.isFile()) {
                fileSystemMonitorModel.setFileCount(fileSystemMonitorModel.getFileCount() + 1);
                String name = file1.getName();
                List<FileSystemMonitorBackupItemModel> backupItems = fileSystemMonitorBackupItemRepository.find(fileSystemMonitorModel.getId(), name);

                if (isUpdate(backupItems, file1.lastModified())) {
                    backup(file1, fileSystemMonitorModel, null);
                }
            } else if (file1.isDirectory()) {
                fileSystemMonitorModel.setFolderCount(fileSystemMonitorModel.getFolderCount() + 1);

                String name = file1.getName();

                Optional<FileSystemMonitorBackupItemModel> folder = fileSystemMonitorBackupItemRepository.findFolder(fileSystemMonitorModel.getId(), name);
                FileSystemMonitorBackupItemModel fileSystemMonitorBackupItemModel = folder.orElse(null);

                FileSystemMonitorBackupItemModel folderBackupItem = fileSystemMonitorBackupItemModel == null ?
                        saveBackupItem(fileSystemMonitorModel, name, null) : fileSystemMonitorBackupItemModel;

                directory(folderBackupItem, fileSystemMonitorModel);
            }
        }
    }

    private FileSystemMonitorBackupItemModel saveBackupItem(FileSystemMonitorModel fileSystemMonitorModel, String name, FileSystemMonitorBackupItemModel parent) {
        FileSystemMonitorBackupItemModel fileSystemMonitorBackupItemModel1 = new FileSystemMonitorBackupItemModel();
        fileSystemMonitorBackupItemModel1.setFileSystemMonitorId(fileSystemMonitorModel.getId());
        fileSystemMonitorBackupItemModel1.setPathType(PathType.FOLDER.getCode());
        fileSystemMonitorBackupItemModel1.setCreatedTime(new Date());
        fileSystemMonitorBackupItemModel1.setDeleteStatus(false);
        fileSystemMonitorBackupItemModel1.setPathName(name);
        if (parent != null) {
            fileSystemMonitorBackupItemModel1.setPatent(parent.getId());
        }
        fileSystemMonitorBackupItemRepository.save(fileSystemMonitorBackupItemModel1);
        return fileSystemMonitorBackupItemModel1;
    }

    private static boolean isUpdate(List<FileSystemMonitorBackupItemModel> backupItems, long lastModified) {
        for (FileSystemMonitorBackupItemModel backupItem : backupItems) {
            if (backupItem.getLastModified() == lastModified) {
                return false;
            }
        }
        return true;
    }

    private void directory(FileSystemMonitorBackupItemModel folderBackupItem, FileSystemMonitorModel fileSystemMonitorModel) {

        String folderPath = getAbsolutePath(folderBackupItem, fileSystemMonitorModel);
        File file = new File(folderPath);

        File[] files = file.listFiles();
        if (files != null) {
            for (File file1 : files) {
                if (file1.isFile()) {
                    fileSystemMonitorModel.setFileCount(fileSystemMonitorModel.getFileCount() + 1);
                    String name = file1.getName();

                    List<FileSystemMonitorBackupItemModel> fileSystemMonitorBackupItemModels = fileSystemMonitorBackupItemRepository.find(fileSystemMonitorModel.getId(), name, folderBackupItem.getId());

                    if (isUpdate(fileSystemMonitorBackupItemModels, file1.lastModified())) {
                        backup(file1, fileSystemMonitorModel, folderBackupItem);
                    }
                } else if (file1.isDirectory()) {
                    fileSystemMonitorModel.setFolderCount(fileSystemMonitorModel.getFolderCount() + 1);

                    String name = file1.getName();

                    Optional<FileSystemMonitorBackupItemModel> folder = fileSystemMonitorBackupItemRepository.findFolder(fileSystemMonitorModel.getId(), name, folderBackupItem.getId());
                    FileSystemMonitorBackupItemModel fileSystemMonitorBackupItemModel = folder.orElse(null);

                    FileSystemMonitorBackupItemModel folderBackupItem2 = fileSystemMonitorBackupItemModel == null ?
                            saveBackupItem(fileSystemMonitorModel, name, folderBackupItem) : fileSystemMonitorBackupItemModel;

                    directory(folderBackupItem2, fileSystemMonitorModel);
                }
            }
        }
    }

    String getAbsolutePath(FileSystemMonitorBackupItemModel folderBackupItem, FileSystemMonitorModel fileSystemMonitorModel) {
        StringBuilder temp = new StringBuilder(fileSystemMonitorModel.getPathName() + File.separator);
        List<FileSystemMonitorBackupItemModel> folders = new ArrayList<>();
        folders.add(folderBackupItem);

        getFolderNames(folderBackupItem, folders);
        for (int i = folders.size() - 1; i >= 0; i--) {
            temp.append(folders.get(i).getPathName());
            if (i > 0) {
                temp.append(File.separator);
            }
        }

        return temp.toString();
    }

    private void getFolderNames(FileSystemMonitorBackupItemModel folderBackupItem, List<FileSystemMonitorBackupItemModel> folders) {
        if (folderBackupItem.getPatent() == null) {
            return;
        }

        FileSystemMonitorBackupItemModel fileSystemMonitorBackupItemModel = fileSystemMonitorBackupItemRepository.findById(folderBackupItem.getPatent()).orElseThrow(NullPointerException::new);
        folders.add(fileSystemMonitorBackupItemModel);
        if (fileSystemMonitorBackupItemModel.getPatent() != null) {
            getFolderNames(fileSystemMonitorBackupItemModel, folders);
        }
    }

    private void backup(File file, FileSystemMonitorModel fileSystemMonitorModel, FileSystemMonitorBackupItemModel parent) {
        fileSystemMonitorModel.setLastBackup(file.getName());

        String patent = systemConfigService.backupFolderRoot(); // 备份文件夹根路径

        String format = SDF.format(new Date()); // 日期路径

        UUID fileName = UUID.randomUUID(); // 备份文件名

        File file1 = new File(patent + File.separator + format);
        if (!file1.isDirectory() && !file1.mkdirs()) {
            throw new IllegalArgumentException("目录创建失败");
        }

        File dstFile = new File(patent + File.separator + format + File.separator + fileName);

        FileOutputStream fileOutputStream = null;
        FileInputStream fileInputStream = null;
        try {
            fileOutputStream = new FileOutputStream(dstFile);
            fileInputStream = new FileInputStream(file);
            IoUtil.copy(fileInputStream, fileOutputStream);
        } catch (Exception e) {
            throw new IllegalArgumentException("备份失败" + e.getMessage());
        } finally {
            IOUtil.close(fileOutputStream, fileInputStream);
        }

        saveItem(file, fileSystemMonitorModel, (format + File.separator + fileName), parent);
    }

    private void saveItem(File file, FileSystemMonitorModel fileSystemMonitorModel, String backupFilePath, FileSystemMonitorBackupItemModel parent) {
        FileSystemMonitorBackupItemModel fileSystemMonitorBackupItemModel = new FileSystemMonitorBackupItemModel();
        fileSystemMonitorBackupItemModel.setFileSystemMonitorId(fileSystemMonitorModel.getId());
        fileSystemMonitorBackupItemModel.setPathType(PathType.FILE.getCode());
        fileSystemMonitorBackupItemModel.setLastModified(file.lastModified());
        fileSystemMonitorBackupItemModel.setCreatedTime(new Date());
        fileSystemMonitorBackupItemModel.setPathName(file.getName());
        fileSystemMonitorBackupItemModel.setBackupFile(backupFilePath);
        fileSystemMonitorBackupItemModel.setDeleteStatus(false);
        if (parent != null) {
            fileSystemMonitorBackupItemModel.setPatent(parent.getId());
        }

        fileSystemMonitorBackupItemRepository.save(fileSystemMonitorBackupItemModel);
    }

    private void pathNotExists(FileSystemMonitorModel fileSystemMonitorModel) {
        FileSystemMonitorMessageModel fileSystemMonitorMessageModel = new FileSystemMonitorMessageModel();
        fileSystemMonitorMessageModel.setFileSystemMonitorId(fileSystemMonitorModel.getId());
        fileSystemMonitorMessageModel.setMessageLevel(1);
        fileSystemMonitorMessageModel.setMessage("需要监听的目录不存在");
        fileSystemMonitorMessageModel.setCreatedTime(new Date());
        fileSystemMonitorMessageModel.setDeleteStatus(false);
        fileSystemMonitorMessageRepository.save(fileSystemMonitorMessageModel);

        fileSystemMonitorModel.setStatus(FileSystemStatus.STOP.getCode());
        fileSystemMonitorModel.setMonitor(false);
        fileSystemMonitorRepository.save(fileSystemMonitorModel);
    }
}
